﻿//
// Copyright (c) 2011 Western Data Solutions (www.westerndatasolutions.com) (Armen Shimoon)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without 
// limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 
// the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 
// THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 
// OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace WDS.Bencode
{

    public class BencodeList : BencodeList<BencodeElement> {}

    /// <summary>
    /// A bencoded representation of a list
    /// </summary>
    public class BencodeList<TBencodeElement> : BencodeElement, IComparable<BencodeList<TBencodeElement>>, IList<TBencodeElement> where TBencodeElement : BencodeElement
    {
        /// <summary>
        /// Internal representation of the list
        /// </summary>
        private readonly List<TBencodeElement> m_Elements = null;

        /// <summary>
        /// Public access to the list
        /// </summary>
        public IList<TBencodeElement> Elements
        {
            get{return m_Elements;}
        }

        /// <summary>
        /// Default constructor
        /// </summary>
        public BencodeList()
        {
            m_Elements = new List<TBencodeElement>();
        }

        /// <summary>
        /// Defautl property
        /// </summary>
        /// <param name="index">Index of the list item</param>
        /// <returns>The list item requested</returns>
        public TBencodeElement this[int index]
        {
            get{return m_Elements[index];}
            set{m_Elements[index] = value;}
        }

        /// <summary>
        /// Adds an element to the list
        /// </summary>
        /// <param name="element">The element to add</param>
        public void Add(TBencodeElement element)
        {
            m_Elements.Add(element);
        }

        /// <summary>
        /// Clears the list
        /// </summary>
        public void Clear()
        {
            m_Elements.Clear();
        }

        /// <summary>
        /// Determines whether an element is in the list
        /// </summary>
        /// <param name="item">The list item to search for</param>
        /// <returns>True if the element is in the list</returns>
        public bool Contains(TBencodeElement item)
        {
            return m_Elements.Contains(item);
        }

        /// <summary>
        /// Copies the list items into an array
        /// </summary>
        /// <param name="array">The array to copy to</param>
        /// <param name="arrayIndex">The starting index of the array</param>
        public void CopyTo(TBencodeElement[] array, int arrayIndex)
        {
            m_Elements.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// Remvoes an element from the list
        /// </summary>
        /// <param name="item">The element to remvoe</param>
        /// <returns>True if the element was removed</returns>
        bool ICollection<TBencodeElement>.Remove(TBencodeElement item)
        {
            return m_Elements.Remove(item);
        }

        /// <summary>
        /// The number of element in the list
        /// </summary>
        public int Count
        {
            get { return m_Elements.Count; }
        }

        /// <summary>
        /// Determines whether the list is read only
        /// </summary>
        public bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// Adds an element to the list at the specified position
        /// </summary>
        /// <param name="index">The position to add the element</param>
        /// <param name="element">The element to add</param>
        public void AddAt(int index, TBencodeElement element)
        {
            m_Elements.Insert(index, element);
        }

        /// <summary>
        /// Adds a list of element to the list
        /// </summary>
        /// <param name="elements">The list of elements to add</param>
        public void AddRange(IEnumerable<TBencodeElement> elements)
        {
            m_Elements.AddRange(elements);
        }

        /// <summary>
        /// Removes an element from the list
        /// </summary>
        /// <param name="element">The element to remove</param>
        public void Remove(TBencodeElement element)
        {
            var toRemove =
                (from item in m_Elements
                 where item.CompareTo(element) == 0
                 select item).ToList();

            foreach (var bencodeElement in toRemove)
            {
                m_Elements.Remove(bencodeElement);
            }
        }

        /// <summary>
        /// Determines the index of a desired element
        /// </summary>
        /// <param name="item">The desired element</param>
        /// <returns>The index if found, -1 if not found</returns>
        public int IndexOf(TBencodeElement item)
        {
            return m_Elements.IndexOf(item);
        }

        /// <summary>
        /// Inserts an element into the list
        /// </summary>
        /// <param name="index">The index to insert at</param>
        /// <param name="item">The element to insert</param>
        public void Insert(int index, TBencodeElement item)
        {
            m_Elements.Insert(index, item);
        }

        /// <summary>
        /// Removes an element at the specified index
        /// </summary>
        /// <param name="index">Index to remove at</param>
        public void RemoveAt(int index)
        {
            m_Elements.RemoveAt(index);
        }

        /// <summary>
        /// Compares the list to another list
        /// </summary>
        /// <param name="other">The list to compare to</param>
        /// <returns>0 if the lists are equal</returns>
        public int CompareTo(BencodeList<TBencodeElement> other)
        {
            if (m_Elements == null || other.m_Elements == null)
            {
                return 1;
            }
            if (!m_Elements.Count.Equals(other.m_Elements.Count))
            {
                return 1;
            }
            // assuming two lists are only equal if their elements are in the same order

            for (int i = 0; i < m_Elements.Count; i++)
            {
                int difference = m_Elements[i].CompareTo(other.m_Elements[i]);
                if (difference != 0)
                {
                    return 1;
                }
            }

            return 0;
        }

        /// <summary>
        /// The enumerator for the elements
        /// </summary>
        /// <returns>Strongly typed enumerator</returns>
        public IEnumerator<TBencodeElement> GetEnumerator()
        {
            return m_Elements.GetEnumerator();
        }

        /// <summary>
        /// The weakly typed enumerator for the elements
        /// </summary>
        /// <returns>Weakly typed enumerator</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Returns a list element at an index as a given type
        /// </summary>
        /// <typeparam name="T">The type to return the element as</typeparam>
        /// <param name="index">The index of the element</param>
        /// <returns>A strongly typed bencoded element</returns>
        protected T ItemOfType<T>(int index) where T : BencodeElement
        {
            if (index >= Count)
            {
                return (T)NullOf<T>();
            }

            return TryReturnAs<T>(this[index]);
            
        }

        /// <summary>
        /// Sets an item at an index, even if it is a string or integer (auto bencoding)
        /// </summary>
        /// <typeparam name="T">The type of the value</typeparam>
        /// <param name="index">The index to add the iteem at</param>
        /// <param name="value">The value of the item</param>
        protected void SetItem<T>(int index, T value)
        {
            this[index] = (TBencodeElement)BuildElementWithValue(value);
        }


   
    }
}
